#include "c.h"

#include <sys/stat.h>

#include "pgtar.h"

/*
 * 在tar头中打印一个数值字段。字段从 *s 开始，长度为 len；val 是要写入的值。
 *
 * 按照POSIX，写数字的方法是以八进制形式表示，前面有零，后面有一个空格（或NUL，但我们用空格）在指定字段宽度的末尾。
 *
 * 然而，给定的值可能无法以八进制形式适应可用空间。如果这样的话，我们使用GNU扩展，写 \200 后跟以基数256形式表示的数字（即，按二进制MSB优先存储）。（注意：在这里我们只支持非负数，因此我们不需要担心GNU规则处理负数的问题。）
 */
void print_tar_number(char *fc_s, int fc_len, uint64 fc_val)
{
	if (fc_val < (((uint64) 1) << ((fc_len - 1) * 3)))
	{
		/* 使用八进制，后面带空格 */
		fc_s[--fc_len] = ' ';
		while (fc_len)
		{
			fc_s[--fc_len] = (fc_val & 7) + '0';
			fc_val >>= 3;
		}
	}
	else
	{
		/* 使用基数256，前面带\200 */
		fc_s[0] = '\200';
		while (fc_len > 1)
		{
			fc_s[--fc_len] = (fc_val & 255);
			fc_val >>= 8;
		}
	}
}


/*
 * 在tar头中读取一个数值字段。字段从 *s 开始，长度为 len。
 *
 * POSIX批准的数字格式是八进制，以空格或NUL结束。然而，对于那些无法适应的值，我们识别GNU扩展，即 \200 后跟以基数256形式表示的数字（即，按二进制MSB优先存储）。（注意：在这里我们只支持非负数，因此我们不需要担心GNU规则处理负数的问题。）
 */
uint64 read_tar_number(const char *fc_s, int fc_len)
{
	uint64		fc_result = 0;

	if (*fc_s == '\200')
	{
		/* 基数256 */
		while (--fc_len)
		{
			fc_result <<= 8;
			fc_result |= (unsigned char) (*++fc_s);
		}
	}
	else
	{
		/* 八进制 */
		while (fc_len-- && *fc_s >= '0' && *fc_s <= '7')
		{
			fc_result <<= 3;
			fc_result |= (*fc_s - '0');
			fc_s++;
		}
	}
	return fc_result;
}


/*
 * 计算头的tar校验和。根据tar标准，头的大小始终假设为512字节。
 */
int tarChecksum(char *fc_header)
{
	int			fc_i,
				fc_sum;

	/*
	 * 根据POSIX，校验和是头中所有字节的简单总和，
	 * 将字节视为无符号，并将校验和字段（在偏移量148处）视为
	 * 好像它包含8个空格。
	 */
	fc_sum = 8 * ' ';				/* 校验和字段的假定值 */
	for (fc_i = 0; fc_i < 512; fc_i++)
		if (fc_i < 148 || fc_i >= 156)
			fc_sum += 0xFF & fc_header[fc_i];
	return fc_sum;
}


/*
 * 用tar格式头填充指向h的缓冲区。这个缓冲区
 * 必须始终有512个字符的空间，这是tar格式的要求。
 */
enum tarError
tarCreateHeader(char *fc_h, const char *fc_filename, const char *fc_linktarget,
				pgoff_t fc_size, mode_t fc_mode, uid_t fc_uid, gid_t fc_gid, time_t fc_mtime)
{
	if (strlen(fc_filename) > 99)
		return TAR_NAME_TOO_LONG;

	if (fc_linktarget && strlen(fc_linktarget) > 99)
		return TAR_SYMLINK_TOO_LONG;

	memset(fc_h, 0, 512);			/* 假设tar头大小 */

	/* 名称 100 */
	strlcpy(&fc_h[0], fc_filename, 100);
	if (fc_linktarget != NULL || S_ISDIR(fc_mode))
	{
		/*
		 * 我们只支持指向目录的符号链接，这在
		 * tar格式中通过在名称末尾添加斜杠来表示，这与常规目录相同。
		 */
		int			fc_flen = strlen(fc_filename);

		fc_flen = Min(fc_flen, 99);
		fc_h[fc_flen] = '/';
		fc_h[fc_flen + 1] = '\0';
	}

	/* 模式 8 - 这不包括文件类型位 (S_IFMT) */
	print_tar_number(&fc_h[100], 8, (fc_mode & 07777));

	/* 用户ID 8 */
	print_tar_number(&fc_h[108], 8, fc_uid);

	/* 组 8 */
	print_tar_number(&fc_h[116], 8, fc_gid);

	/* 文件大小 12 */
	if (fc_linktarget != NULL || S_ISDIR(fc_mode))
		/* 符号链接或目录的大小为零 */
		print_tar_number(&fc_h[124], 12, 0);
	else
		print_tar_number(&fc_h[124], 12, fc_size);

	/* 修改时间 12 */
	print_tar_number(&fc_h[136], 12, fc_mtime);

	/* 在填充所有其他字段之前无法计算校验和 8 */

	if (fc_linktarget != NULL)
	{
		/* 类型 - 符号链接 */
		fc_h[156] = '2';
		/* 链接名称 100 */
		strlcpy(&fc_h[157], fc_linktarget, 100);
	}
	else if (S_ISDIR(fc_mode))
	{
		/* 类型 - 目录 */
		fc_h[156] = '5';
	}
	else
	{
		/* 类型 - 常规文件 */
		fc_h[156] = '0';
	}

	/* 魔术 6 */
	strcpy(&fc_h[257], "ustar");

	/* 版本 2 */
	memcpy(&fc_h[263], "00", 2);

	/* 用户 32 */
	/* XXX: 我们是否需要关心设置正确的用户名？ */
	strlcpy(&fc_h[265], "postgres", 32);

	/* 组 32 */
	/* XXX: 我们是否需要关心设置正确的组名？ */
	strlcpy(&fc_h[297], "postgres", 32);

	/* 主设备 8 */
	print_tar_number(&fc_h[329], 8, 0);

	/* 次设备 8 */
	print_tar_number(&fc_h[337], 8, 0);

	/* 前缀 155 - 不使用，留为空 */

	/* 最后，计算并插入校验和 */
	print_tar_number(&fc_h[148], 8, tarChecksum(fc_h));

	return TAR_OK;
}
